package mdb

import (
	"database/sql"
	"fmt"
	"gitee.com/dennis-mxx/mxx-core/mexception"
	"gitee.com/dennis-mxx/mxx-core/mlogger"
	"gitee.com/dennis-mxx/mxx-core/mmodel"
	"gitee.com/dennis-mxx/mxx-core/mutil"
	"github.com/jmoiron/sqlx"
)

type SQLExecute struct {
	DB              *sqlx.DB
	TransactionalId string
	ShowSql         bool
	Transactional   *sqlx.Tx
}
type Counter struct {
	Count int64 `db:"count"`
}

func (domain Counter) OK() bool {
	return domain.Count > 0
}
func (domain Counter) Nil() bool {
	return domain.Count == 0
}

func (domain *SQLExecute) OpenTransactional() *SQLExecute {
	tx := domain.DB.MustBegin()
	return &SQLExecute{
		DB:              domain.DB,
		TransactionalId: mutil.RandomUtil.UniqueId("TX", 20),
		Transactional:   tx,
	}
}

func (domain *SQLExecute) Rollback() *SQLExecute {
	if domain.Transactional != nil {
		err := domain.Transactional.Rollback()
		if err != nil {
			mlogger.Logger.Error(fmt.Sprintf("txId:%s , rallback failure", domain.TransactionalId))
			panic(mexception.NewErrorEx(mexception.SQLException, err))
		}
		if domain.ShowSql {
			mlogger.Logger.Info(fmt.Sprintf("txId:%s , rallback successful", domain.TransactionalId))
		}
		return domain
	}
	panic(mexception.NewException(mexception.SQLException, "rollback transactional is nil"))
}
func (domain *SQLExecute) Commit() *SQLExecute {
	if domain.Transactional != nil {
		err := domain.Transactional.Commit()
		if err != nil {
			mlogger.Logger.Error(fmt.Sprintf("txId:%s , commit failure", domain.TransactionalId))
			panic(mexception.NewErrorEx(mexception.SQLException, err))
		}
		if domain.ShowSql {
			mlogger.Logger.Info(fmt.Sprintf("txId:%s , commit successful", domain.TransactionalId))
		}
		return domain
	}
	panic(mexception.NewException(mexception.SQLException, "commit failure transactional is nil"))
}
func (domain *SQLExecute) UpdateMap(tableName string, update map[string]interface{}, where map[string]interface{}) (rowsAffect int64) {
	updateBuild := NewUpdate(tableName).Set(update)
	selectBuild := NewSelect(tableName).Equal(where)
	var sql string
	var params []interface{}
	if len(update) == 0 {
		sql = updateBuild.UpdateSql()
		params = append(params, updateBuild.params)
	} else {
		sql = fmt.Sprintf("%s %s", updateBuild.UpdateSql(), selectBuild.WhereSql())
		params = append(params, updateBuild.UpdateParam()...)
		params = append(params, selectBuild.SelectParam()...)
	}
	return domain.Update(sql, params...)
}
func (domain *SQLExecute) Update(updateSql string, params ...interface{}) (rowsAffect int64) {
	domain.showSql(updateSql, params...)
	var result sql.Result
	if domain.Transactional != nil {
		result = domain.Transactional.MustExec(updateSql, params...)
	} else {
		result = domain.DB.MustExec(updateSql, params...)
	}
	rowsAffect, err := result.RowsAffected()
	if err != nil {
		panic(mexception.NewErrorEx(mexception.SQLException, err))
	}
	return rowsAffect
}
func (domain *SQLExecute) InsertMap(tableName string, entity interface{}) (rowsAffect int64, lastId int64) {
	insert := NewInsert(tableName)
	insertSql := insert.Insert(entity)
	params := insert.Params()
	return domain.Insert(insertSql, params...)
}
func (domain *SQLExecute) Insert(insertSql string, params ...interface{}) (rowsAffect int64, lastId int64) {
	domain.showSql(insertSql, params...)
	var result sql.Result
	if domain.Transactional != nil {
		result = domain.Transactional.MustExec(insertSql, params...)
	} else {
		result = domain.DB.MustExec(insertSql, params...)
	}
	rowsAffect, err := result.RowsAffected()
	if err != nil {
		panic(mexception.NewErrorEx(mexception.SQLException, err))
	}
	lastId, errs := result.LastInsertId()

	if errs != nil {
		panic(mexception.NewErrorEx(mexception.SQLException, errs))
	}
	return rowsAffect, lastId
}
func (domain *SQLExecute) Delete(tableName string, where map[string]interface{}) (rowsAffect int64) {
	query := NewSelect(tableName)
	whereSql := query.Equal(where).WhereSql()
	params := query.SelectParam()
	deleteSql := fmt.Sprintf("delete from %s %s", tableName, whereSql)
	domain.showSql(deleteSql, params...)
	var result sql.Result
	if domain.Transactional != nil {
		result = domain.Transactional.MustExec(deleteSql, params...)
	} else {
		result = domain.DB.MustExec(deleteSql, params...)
	}
	rowsAffect, err := result.RowsAffected()
	if err != nil {
		panic(mexception.NewErrorEx(mexception.SQLException, err))
	}
	return rowsAffect
}
func (domain *SQLExecute) Count(tableName string, where map[string]interface{}) (count Counter) {
	countStruct := Counter{}
	query := NewSelect(tableName).Equal(where)

	domain.SelectRow(&countStruct, query.CountSql(), query.SelectParam()...)

	return countStruct
}

func (domain *SQLExecute) NotFound(tableName string, where map[string]interface{}) (notFound bool) {
	count := domain.Count(tableName, where)
	return count.Nil()
}
func (domain *SQLExecute) Exists(tableName string, where map[string]interface{}) (exists bool) {
	count := domain.Count(tableName, where)
	return count.OK()
}

func (domain *SQLExecute) SelectById(entity interface{}, tableName string, idName string, idValue interface{}) {
	sql := fmt.Sprintf("select * from %s where %s = ?", tableName, idName)
	domain.SelectRow(entity, sql, idValue)
}

func (domain *SQLExecute) SelectPage(entity interface{}, query *query, page mmodel.Page) *mmodel.Pageable {
	countStruct := Counter{}

	domain.SelectRow(&countStruct, query.CountSql(), query.SelectParam()...)
	count := countStruct.Count
	longSize := int64(page.Size)
	longPage := int64(page.Page)
	m := count / longSize
	if count%longSize > 0 {
		m += 1
	}
	if longPage >= m {
		return &mmodel.Pageable{
			Page:           page.Page,
			Size:           page.Size,
			Content:        make([]interface{}, 0),
			First:          true,
			Last:           true,
			MaxPage:        int(m - 1),
			TotalElement:   count,
			CurrentElement: 0,
		}
	} else {
		var currentElement int
		end := int64(page.Page*page.Size + page.Size)
		if end > count {
			currentElement = page.Size - int(end-count)
		} else {
			currentElement = page.Size
		}
		query.Limit(page.Page*page.Size, page.Size)
		domain.SelectList(entity, query.SelectSql(), query.SelectParam()...)
		return &mmodel.Pageable{
			Page:           page.Page,
			Size:           page.Size,
			Content:        entity,
			First:          page.Page == 0,
			Last:           longPage == m-1,
			MaxPage:        int(m - 1),
			TotalElement:   count,
			CurrentElement: currentElement,
		}
	}

}

func (domain *SQLExecute) SelectList(entity interface{}, sql string, params ...interface{}) {
	domain.showSql(sql, params...)
	var err error

	if domain.Transactional != nil {
		err = domain.Transactional.Select(entity, sql, params...)
	} else {
		err = domain.DB.Select(entity, sql, params...)
	}
	if err != nil {
		panic(mexception.NewErrorEx(mexception.SQLException, err))
	}
}
func (domain *SQLExecute) SelectRow(entity interface{}, sql string, params ...interface{}) {
	domain.showSql(sql, params...)
	var err error
	if domain.Transactional != nil {
		err = domain.Transactional.Get(entity, sql, params...)
	} else {
		err = domain.DB.Get(entity, sql, params...)
	}
	if err != nil && err.Error() == `sql: no rows in result set` {
		return
	}
	if err != nil {
		panic(mexception.NewErrorEx(mexception.SQLException, err))
	}
}

func (domain *SQLExecute) showSql(sql string, param ...interface{}) {
	if domain.ShowSql {
		if domain.Transactional != nil {
			mlogger.Logger.Info(fmt.Sprintf("txId:%s , sql:%s ---> %v", domain.TransactionalId, sql, param))
		} else {
			mlogger.Logger.Info(fmt.Sprintf("%s ---> %v", sql, param))
		}
	}
}
